package com.tramp.basic.service;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import javax.annotation.PreDestroy;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import com.google.common.collect.Lists;
import com.tramp.basic.entity.OperateLogAction;
import com.tramp.basic.vo.OperateLogDataVO;
import com.tramp.basic.vo.OperateLogVO;
import com.tramp.frame.server.exception.GenericException;
import com.tramp.utils.JSONUtils;
import com.tramp.utils.ShiroUtils;

/**
 * 操作日志 Service
 * @author chenjm
 * @since 2017-11-22
 */
@Service
public class OperateLogService {

	//每次执行插入数据库，最小数据量
	private static final int EACH_INSERT_MIN_NUM = 10;

	//允许缓存错误记录的最大量，超过该值则直接丢弃
	private static final int FAIL_CACHE_MAX_NUM = 100;

	//第1个参数
	public static final int PARAM_INDEX_ONE = 1;
	//第2个参数
	public static final int PARAM_INDEX_TWO = 2;
	//第3个参数
	public static final int PARAM_INDEX_THREE = 3;
	//第4个参数
	public static final int PARAM_INDEX_FOUR = 4;
	//第5个参数
	public static final int PARAM_INDEX_FIVE = 5;
	//第6个参数
	public static final int PARAM_INDEX_SIX = 6;
	//第7个参数
	public static final int PARAM_INDEX_SEVEN = 7;

	private final Consumer consumer;

	//不记录日志的Url
	private static final List<String> NO_RECORD_LOG_URL_LIST = Lists.newArrayList("/message/getInstantMessages" //消息弹框
	);

	//todo 定时任务的初始化设计
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private JdbcTemplate jdbcTemplate;

	//全局操作日志缓存队列
	private static BlockingQueue<OperateLogVO> operateLogViewBlockingQueue;

	//当前线程的访问url信息
	private ThreadLocal<OperateLogAction> operateLogActionThread = new ThreadLocal<OperateLogAction>() {
		@Override
		public OperateLogAction initialValue() {
			return new OperateLogAction();
		}
	};

	//当前线程的数据操作记录
	private ThreadLocal<List<OperateLogDataVO>> operateLogDataListThread = new ThreadLocal<List<OperateLogDataVO>>() {
		@Override
		public List<OperateLogDataVO> initialValue() {
			return new ArrayList<>();
		}
	};

	public OperateLogService() {
		setOperateLogViewBlockingQueue(new LinkedBlockingQueue<OperateLogVO>());
		this.consumer = new Consumer();
		this.consumer.start();
	}

	private static void setOperateLogViewBlockingQueue(BlockingQueue<OperateLogVO> operateLogViewBlockingQueue) {
		OperateLogService.operateLogViewBlockingQueue = operateLogViewBlockingQueue;
	}

	public void init() {
		if (null != operateLogActionThread) {
			operateLogActionThread.remove();
		}
		if (null != operateLogDataListThread) {
			operateLogDataListThread.remove();
		}
	}

	@PreDestroy
	public void destroy() {
		try {
			consumer.doInsertDb();
			this.consumer.destory();
		}
		catch (Exception e) {
			logger.error("OperateLogService destroy fail!", e);
		}
		if (null != operateLogViewBlockingQueue) {
			operateLogViewBlockingQueue.clear();
			setOperateLogViewBlockingQueue(null);
		}
		if (null != operateLogActionThread) {
			operateLogActionThread.remove();
		}
		if (null != operateLogDataListThread) {
			operateLogDataListThread.remove();
		}
	}

	/**
	 * 添加用户action访问日志
	 * @param url 操作的action url
	 */
	public void addOperateLogAction(String url, String params) {
		try {
			if (null == operateLogActionThread) {
				logger.error("OperateLogService has not been properly initialized!url:{}", url);
				throw new GenericException("OperateLogService has not been properly initialized!");
			}
			OperateLogAction entity = new OperateLogAction();
			String operateUser = ShiroUtils.getUserName();
			entity.setOperateUser(operateUser);
			entity.setOperateTime(new Date());
			entity.setUrl(url);
			entity.setParams(params);
			operateLogActionThread.set(entity);
		}
		catch (Exception e) {
			logger.warn("addOperateLogAction fail!url:{}", url, e);
		}
	}

	/**
	 * 添加用户操作数据的日志
	 * @param operateType 操作类型
	 * @param classStr	操作对象的类路径
	 * @param operateObj	操作的具体对象
	 */
	public void addOperateLogData(String operateType, String classStr, Object operateObj) {
		try {

			if (null == operateLogDataListThread || null == operateLogDataListThread.get()) {
				logger.error("OperateLogService has not been properly initialized!operateType:{},classStr:{},operateObj:{}", operateType, classStr,
						operateObj);
				throw new GenericException("OperateLogService has not been properly initialized!");
			}
			OperateLogDataVO entity = new OperateLogDataVO();
			entity.setOperateTime(new Date());
			entity.setOperateType(operateType);
			entity.setOperateClass(classStr);
			entity.setOperateObj(operateObj);
			String operateUser = ShiroUtils.getUserName();
			entity.setOperateUser(operateUser);
			operateLogDataListThread.get().add(entity);
		}
		catch (Exception e) {
			logger.warn("addOperateLogData fail!", e);
		}
	}

	/**
	 * 提交操作记录至待插入数据的队列中
	 * @throws InterruptedException
	 */
	public void commitToBlockingQueue() {
		try {
			Integer size = 0;
			OperateLogVO log = new OperateLogVO();
			OperateLogAction operateLogActionEntity = operateLogActionThread.get();
			String url = null;
			if (null != operateLogActionEntity) {
				url = operateLogActionEntity.getUrl();
			}
			if (NO_RECORD_LOG_URL_LIST.contains(url)) {
				return;
			}
			log.setOperateLogActionEntity(operateLogActionEntity);
			size++;

			List<OperateLogDataVO> list = operateLogDataListThread.get();
			if (CollectionUtils.isNotEmpty(list)) {
				size = size + list.size();
			}
			else {
				//如果是定时任务的请求，并且没有修改数据库，则不记录
				if (StringUtils.startsWith(url, "JOB:")) {
					return;
				}
			}
			if (size <= 0) {
				return;
			}
			log.setSize(size);
			log.setOperateLogDataList(list);
			operateLogViewBlockingQueue.put(log);
		}
		catch (Exception e) {
			logger.error("commit fail!", e);
		}
		finally {
			if (null != operateLogActionThread) {
				operateLogActionThread.remove();
			}
			if (null != operateLogDataListThread) {
				operateLogDataListThread.remove();
			}
		}

	}

	/**
	 * 消费线程——消费队列中等待插入到数据库的数据
	 */
	private class Consumer extends Thread {
		private static final String INSERT_ACTION_SQL = "INSERT INTO OPERATE_LOG_ACTION (ACTION_SN,OPERATE_USER,OPERATE_TIME,URL,PARAMS)VALUES(?,?,?,?,?)";
		private static final String INSERT_DATA_SQL = "INSERT INTO OPERATE_LOG_DATA (ACTION_SN,OPERATE_USER,OPERATE_TIME,OPERATE_TYPE,OPERATE_CLASS,OPERATE_JSON)"
				+ "VALUES(?,?,?,?,?,?)";

		private List<OperateLogAction> operateLogActionList;
		private List<OperateLogDataVO> operateLogDataList;
		private Integer size = 0;

		Consumer() {
			operateLogActionList = new ArrayList<>();
			operateLogDataList = new ArrayList<>();
		}

		private synchronized void addOperateLogActionList(OperateLogAction entity) {
			operateLogActionList.add(entity);
		}

		private synchronized void addOperateLogDataList(OperateLogDataVO view) {
			operateLogDataList.add(view);
		}

		synchronized void destory() {
			if (null != operateLogActionList) {
				operateLogActionList.clear();
				operateLogActionList = null;
			}
			if (null != operateLogDataList) {
				operateLogDataList.clear();
				operateLogDataList = null;
			}
		}

		/**
		 * 执行消费队列数据
		 */
		@Override
		public void run() {

			while (true) {
				try {
					OperateLogVO log = operateLogViewBlockingQueue.take();
					if (null == log || log.getSize() <= 0) {
						continue;
					}
					size = size + log.getSize();
					final String uuid = UUID.randomUUID().toString();
					if (null != log.getOperateLogAction()) {
						log.getOperateLogAction().setActionSn(uuid);
						addOperateLogActionList(log.getOperateLogAction());
					}
					List<OperateLogDataVO> list = log.getOperateLogDataList();
					if (CollectionUtils.isNotEmpty(list)) {
						for (OperateLogDataVO operateLogDataEntity : list) {
							operateLogDataEntity.setActionSn(uuid);
							addOperateLogDataList(operateLogDataEntity);
						}
					}
					if (size < EACH_INSERT_MIN_NUM) {
						continue;
					}
					size = 0;
					doInsertDb();
				}
				catch (InterruptedException e) {
					logger.error("consumer InterruptedException", e);
					Thread.currentThread().interrupt();
				}
				catch (Exception e) {
					//todo list过大、size过大、连续N次出现异常
					logger.error("consumer fail!", e);
				}
			}

		}

		/**
		 * 执行插入数据库
		 */
		private synchronized void doInsertDb() {
			if (CollectionUtils.isNotEmpty(operateLogActionList)) {
				try {
					jdbcTemplate.batchUpdate(INSERT_ACTION_SQL, new BatchPreparedStatementSetter() {
						public void setValues(PreparedStatement ps, int index) throws SQLException {
							ps.setString(PARAM_INDEX_ONE, operateLogActionList.get(index).getActionSn());
							ps.setString(PARAM_INDEX_TWO, operateLogActionList.get(index).getOperateUser());
							ps.setTimestamp(PARAM_INDEX_THREE, new Timestamp(operateLogActionList.get(index).getOperateTime().getTime()));
							ps.setString(PARAM_INDEX_FOUR, operateLogActionList.get(index).getUrl());
							ps.setString(PARAM_INDEX_FIVE, operateLogActionList.get(index).getParams());
						}

						public int getBatchSize() {
							return operateLogActionList.size();
						}
					});
					operateLogActionList.clear();
				}
				catch (Exception e) {
					logger.error("insertActionLog Fail!", e);
				}
				finally {
					//对没有成功插入数据库的日志进行处理
					if (operateLogActionList.size() >= FAIL_CACHE_MAX_NUM) {
						operateLogActionList.clear(); //todo 记录到log4j日志中
					}
				}
			}
			if (CollectionUtils.isNotEmpty(operateLogDataList)) {
				try {
					jdbcTemplate.batchUpdate(INSERT_DATA_SQL, new BatchPreparedStatementSetter() {
						public void setValues(PreparedStatement ps, int index) throws SQLException {
							ps.setString(PARAM_INDEX_ONE, operateLogDataList.get(index).getActionSn());
							ps.setString(PARAM_INDEX_TWO, operateLogDataList.get(index).getOperateUser());
							ps.setTimestamp(PARAM_INDEX_THREE, new Timestamp(operateLogDataList.get(index).getOperateTime().getTime()));
							ps.setString(PARAM_INDEX_FOUR, operateLogDataList.get(index).getOperateType());
							ps.setString(PARAM_INDEX_FIVE, operateLogDataList.get(index).getOperateClass());
							ps.setString(PARAM_INDEX_SIX, JSONUtils.toJson(operateLogDataList.get(index).getOperateObj()));
						}

						public int getBatchSize() {
							return operateLogDataList.size();
						}
					});
					operateLogDataList.clear();
				}
				catch (Exception e) {
					logger.error("insertDataLog Fail!", e);
				}
				finally {
					if (operateLogDataList.size() >= FAIL_CACHE_MAX_NUM) {
						operateLogDataList.clear(); //todo 记录到log4j日志中
					}
				}

			}
		}
	}

}
